package org.springframework.context.annotation;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.asm.ClassReader;
import org.springframework.asm.Opcodes;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionDefaults;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.xml.XmlReaderContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.NestedIOException;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingServiceMetadataReaderFactory;
import org.springframework.core.type.classreading.ImplementClassMetadataReadingVisitor;
import org.springframework.core.type.classreading.ImplementMetadataReader;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.ServiceClassMetadataReadingVisitor;
import org.springframework.core.type.classwriting.AbstractMetadataWriter;
import org.springframework.core.type.classwriting.ExtendsClassMetadataWriter;
import org.springframework.core.type.classwriting.ImplementInterfaceMetadataWriter;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

import cn.bonoon.kernel.annotations.services.AutoImplements;

public class ServiceConfigDefinitionParser implements BeanDefinitionParser, Opcodes{
	private final Map<Resource, Object> readed = new HashMap<Resource, Object>();
	private final Object hadCached = new Object();
	private Environment environment;
	private ResourcePatternResolver resourcePatternResolver;
	private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();

	private String[] autowireCandidatePatterns;
	private Resource[] findResources(String packageName) throws IOException{
		String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
				ClassUtils.convertClassNameToResourcePath(environment.resolveRequiredPlaceholders(packageName)) + "/**/*.class";
		return resourcePatternResolver.getResources(packageSearchPath);
	}
	
	private void visit(ServiceClassMetadataReadingVisitor item){
		for(String pif : item.getInterfaceNames()){
			waives.put(pif, hadCached);
			ServiceClassMetadataReadingVisitor pi = servCached.get(pif);
			if(null != pi){
				item.add(pi);
				visit(pi);
			}
		}
	}

	private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();

	private BeanDefinitionDefaults beanDefinitionDefaults = new BeanDefinitionDefaults();
	private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();
	private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
	/**
	 * 需要被放弃处理的接口或虚类，因为这些接口或虚类已经有具体的实现类，不需要自动生成
	 */
	private final Map<String, Object> waives = new HashMap<String, Object>();
	private final Map<String, ServiceClassMetadataReadingVisitor> servCached = new HashMap<String, ServiceClassMetadataReadingVisitor>();
	private CachingServiceMetadataReaderFactory metadataReaderFactory;
	private BeanDefinitionRegistry registry;
	
	@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute("base-package"),
					ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS),
				implPackages = StringUtils.tokenizeToStringArray(element.getAttribute("impl-package"),
					ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
		if(null == basePackages || basePackages.length == 0){//默认存放接口的地方
			basePackages = new String[]{"cn.bonoon.core"};
		}
		if(null == implPackages || implPackages.length == 0){//默认存放接口实现的地方
			implPackages = new String[]{"cn.bonoon.services"};
		}
		
		//--------------读取接口
		XmlReaderContext readerContext = parserContext.getReaderContext();
		ResourceLoader resourceLoader = readerContext.getResourceLoader();
		ClassLoader classLoader = resourceLoader.getClassLoader();
		environment = parserContext.getDelegate().getEnvironment();
		resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
		metadataReaderFactory = new CachingServiceMetadataReaderFactory(resourcePatternResolver);
		registry = readerContext.getRegistry();
		registerDefaultFilters();
		try{
			for(String basePackage : basePackages){
				for (Resource resource : findResources(basePackage)) {
					if (noResource(resource)) continue;
					ClassReader classReader = loadClassReader(resource);
					ServiceClassMetadataReadingVisitor visitor = new ServiceClassMetadataReadingVisitor(classLoader);
					classReader.accept(visitor, ClassReader.SKIP_CODE);
					if(visitor.isInterface()){//放弃非接口的
						servCached.put(visitor.getClassName(), visitor);
					}
				}
			}
			//对接口的继承关系进行调整
			for(ServiceClassMetadataReadingVisitor scm : servCached.values()){
				visit(scm);
			}//------------ 到这里接口处理结束了
			
			/*
			 * ------------- 下面处理接口的实现部分
			 * 接口的实现类有三种情况：
			 *   1.完全实现，必须使用@Service进行注解
			 *   2.部分实现，即只实现接口的一部分方法，另一部分方法由框架自动生成
			 *   3.没有实现，需要创建全新的实现类实现所有的方法
			 *     3.1 接口继承了公共接口，必须知道哪种类型的数据库实体（树结构，可排序等）使用相应的父类
			 *     3.2 接口没有继承公共接口，则使用最基本的数据访问父类
			 */
			Set<AnnotatedBeanDefinition> candidates = new LinkedHashSet<AnnotatedBeanDefinition>();
			
			Map<String, ImplementClassMetadataReadingVisitor> implCached = new HashMap<String, ImplementClassMetadataReadingVisitor>();
			List<ImplementMetadataReader> concretes = new ArrayList<ImplementMetadataReader>();
			for(String implPackage : implPackages){
				for (Resource resource : findResources(implPackage)) {
					if (noResource(resource)) continue;
					ImplementMetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
					ImplementClassMetadataReadingVisitor visitor = metadataReader.getClassMetadata();
					if(visitor.isAbstract()){
						implCached.put(visitor.getClassName(), visitor);
					}else if(!visitor.isInterface()){ 
						concretes.add(metadataReader);
						if(isCandidateComponent(metadataReader)){//可能需要被托管的类
							//TODO 对接口和父类处理完成后，就来看一下是否可以生成bean交给spring托管
							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
							sbd.setResource(resource);
							sbd.setSource(resource);
							if (isCandidateComponent(sbd)) {
								candidates.add(sbd);
							}
						}
					}
				}
			}
			for(ImplementMetadataReader imr : concretes){
				//处理父类的问题
				ImplementClassMetadataReadingVisitor pc = imr.getClassMetadata();
				for(;;){
					String[] ins = pc.getInterfaceNames();
					if(null != ins){
						for(String ifs : ins){
							waives.put(ifs, hadCached);
						}
					}
					String scn = pc.getSuperClassName();
					if(null == scn) break;
					waives.put(scn, hadCached);
					pc = implCached.get(scn);
					if(null == pc)break;
				}
			}
			//对虚类进行处理，并把虚类赋给相应的接口，在实现接口的时候，可以直接使用虚类来作为新生类的父类
			//如果存在多个虚类实现了接口，并且没有具体的实现类的，则任意选择一个吧
			for(ImplementClassMetadataReadingVisitor icmr : implCached.values()){
				//处理父类的问题
				String[] ins = icmr.getInterfaceNames();
				if(null != ins){
					for(String ifs : ins){
						ServiceClassMetadataReadingVisitor scmr = servCached.get(ifs);
						if(null != scmr){
							scmr.setImplementClass(icmr);
						}
					}
				}
				ImplementClassMetadataReadingVisitor pc = icmr;
				for(;;){
					if(pc.hasSuperClass()) break;
					String scn = pc.getClassName();
					pc = implCached.get(scn);
					if(null == pc) break;
					ins = pc.getInterfaceNames();
					if(null != ins){
						for(String ifs : ins){
							waives.put(ifs, hadCached);
						}
					}
				}
			}
			//好了。这里只需要处理那些没有被排除掉的接口就可以了。接口里会绑定相应的实现的父类的了
			for(ServiceClassMetadataReadingVisitor scmr : servCached.values()){
				if (waives.containsKey(scmr.getClassName())) continue;
				// TODO 需要被实现的接口
				ImplementClassMetadataReadingVisitor implementClass = scmr.getImplementClass();
				AbstractMetadataWriter classWriter;
				Class<?> siClass = classLoader.loadClass(scmr.getClassName());
				if (null == implementClass) {
					AutoImplements ai = siClass.getAnnotation(AutoImplements.class);
					if (null != ai) {
						classWriter = new ImplementInterfaceMetadataWriter(scmr, siClass, ai);
					} else {
						continue;
					}
				} else {
					classWriter = new ExtendsClassMetadataWriter(implementClass, scmr, siClass);
				}
				if (null != classWriter) {
			        ASMGenericBeanDefinition sbd = new ASMGenericBeanDefinition(classWriter);
	//		        sbd.setBeanClass(classWriter.buildClass());
	//		        sbd.setBeanClassName(beanClassName)
	//				sbd.setResource(resource);
	//				sbd.setSource(resource);
					candidates.add(sbd);
				}
			}
			Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
			for (AnnotatedBeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				String beanName = this.beanNameGenerator.generateBeanName(candidate, registry);
				((GenericBeanDefinition)candidate).applyDefaults(beanDefinitionDefaults);
				if (autowireCandidatePatterns != null) {
					candidate.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));
				}
				AnnotationConfigUtils.processCommonDefinitionAnnotations(candidate);
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, registry);
					beanDefinitions.add(definitionHolder);
					BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
				}
			}
			registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
		}catch(Throwable t){
			t.printStackTrace();
		}
		return null;
	}

	private boolean noResource(Resource resource) {//没有可用的资源
		return readed.put(resource, hadCached) == hadCached || !resource.isReadable();
	}
	
	private ClassReader loadClassReader(Resource resource) throws IOException, NestedIOException {
		InputStream is = new BufferedInputStream(resource.getInputStream());
		ClassReader classReader;
		try {
			classReader = new ClassReader(is);
		}
		catch (IllegalArgumentException ex) {
			throw new NestedIOException("ASM ClassReader failed to parse class file - " +
					"probably due to a new Java class file version that isn't supported yet: " + resource, ex);
		}
		finally {
			is.close();
		}
		return classReader;
	}
	
	protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
		for (TypeFilter tf : this.excludeFilters) {
			if (tf.match(metadataReader, this.metadataReaderFactory)) {
				return false;
			}
		}
		for (TypeFilter tf : this.includeFilters) {
			if (tf.match(metadataReader, this.metadataReaderFactory)) {
				AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
				if (!metadata.isAnnotated(Profile.class.getName())) {
					return true;
				}
				AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);
				return this.environment.acceptsProfiles(profile.getStringArray("value"));
			}
		}
		return false;
	}
	
	@SuppressWarnings("unchecked")
	protected void registerDefaultFilters() {
		this.includeFilters.add(new AnnotationTypeFilter(Component.class));
		ClassLoader cl = ServiceConfigDefinitionParser.class.getClassLoader();
		try {
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) cl.loadClass("javax.annotation.ManagedBean")), false));
		}
		catch (ClassNotFoundException ex) {
			// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
		}
		try {
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) cl.loadClass("javax.inject.Named")), false));
		}
		catch (ClassNotFoundException ex) {
			// JSR-330 API not available - simply skip.
		}
	}
	
	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		return (beanDefinition.getMetadata().isConcrete() && beanDefinition.getMetadata().isIndependent());
	}
	
	protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
		if (!this.registry.containsBeanDefinition(beanName)) {
			return true;
		}
		BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
		BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
		if (originatingDef != null) {
			existingDef = originatingDef;
		}
		if (isCompatible(beanDefinition, existingDef)) {
			return false;
		}
		throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
				"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
				"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
	}
	
	protected boolean isCompatible(BeanDefinition newDefinition, BeanDefinition existingDefinition) {
		return (!(existingDefinition instanceof ScannedGenericBeanDefinition) ||  // explicitly registered overriding bean
				newDefinition.getSource().equals(existingDefinition.getSource()) ||  // scanned same file twice
				newDefinition.equals(existingDefinition));  // scanned equivalent class twice
	}
	
	protected void registerComponents(
			XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {

		Object source = readerContext.extractSource(element);
		CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);

		for (BeanDefinitionHolder beanDefHolder : beanDefinitions) {
			compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));
		}

		Set<BeanDefinitionHolder> processorDefinitions =
				AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
		for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
			compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
		}

		readerContext.fireComponentRegistered(compositeDef);
	}
}
